None
В нашем распоряжении данные сервиса Яндекс.Недвижимость — архив объявлений о продаже квартир в Санкт-Петербурге и соседних населённых пунктов за несколько лет. Нужно научиться определять рыночную стоимость объектов недвижимости. Ваша задача — установить параметры. Это позволит построить автоматизированную систему: она отследит аномалии и мошенническую деятельность.
По каждой квартире на продажу доступны два вида данных. Первые вписаны пользователем, вторые — получены автоматически на основе картографических данных. Например, расстояние до центра, аэропорта, ближайшего парка и водоёма.
**Оглавление:**
В данном разделе нам предстоит сделать следующие шаги:
> тип столбца => преобразуем в нужный тип
> пустые значения => просавляем "0"/находим median или mean/ удаляем
> уникальные значения => корректное написание значений/ явные/неявные дубликаты
> аномалии => восстанавливаем корректное значение/удаляем редкие и выбивающие значения
3. Посчитаю и добавлю в таблицу новые столбцы
'price_per_metr' цена одного квадратного метра;'first_day_exposition_of_week'день недели публикации объявления (0 — понедельник, 1 — вторник и так далее);'first_exposition_month' месяц публикации объявления;first_exposition_year год публикации объявления;type_of_floor тип этажа квартиры (значения — «первый», «последний», «другой»);distance_to_center расстояние до центра города в километрах (переведите из м в км и округлите до целых значений).4. Исследовательский анализ данных
изучу, построю гистограммы и опишу все мои наблюдения для каждого из этих параметров, а именно:
проанализирую столбец days_exposition(срок продажи):
# Подключаем библиотеки
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as pylab
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
import warnings
warnings.filterwarnings('ignore')
import plotly.express as px
import math
import seaborn as sns
# Загружаем датасет
data = pd.read_csv('https://code.s3.yandex.net/datasets/real_estate_data.csv', sep='\t')
# Изучаем таблицу
data.head(30)
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | kitchen_area | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 20 | 13000000.0 | 108.00 | 2019-03-07T00:00:00 | 3 | 2.70 | 16.0 | 51.00 | 8 | NaN | ... | 25.00 | NaN | Санкт-Петербург | 18863.0 | 16028.0 | 1.0 | 482.0 | 2.0 | 755.0 | NaN |
| 1 | 7 | 3350000.0 | 40.40 | 2018-12-04T00:00:00 | 1 | NaN | 11.0 | 18.60 | 1 | NaN | ... | 11.00 | 2.0 | посёлок Шушары | 12817.0 | 18603.0 | 0.0 | NaN | 0.0 | NaN | 81.0 |
| 2 | 10 | 5196000.0 | 56.00 | 2015-08-20T00:00:00 | 2 | NaN | 5.0 | 34.30 | 4 | NaN | ... | 8.30 | 0.0 | Санкт-Петербург | 21741.0 | 13933.0 | 1.0 | 90.0 | 2.0 | 574.0 | 558.0 |
| 3 | 0 | 64900000.0 | 159.00 | 2015-07-24T00:00:00 | 3 | NaN | 14.0 | NaN | 9 | NaN | ... | NaN | 0.0 | Санкт-Петербург | 28098.0 | 6800.0 | 2.0 | 84.0 | 3.0 | 234.0 | 424.0 |
| 4 | 2 | 10000000.0 | 100.00 | 2018-06-19T00:00:00 | 2 | 3.03 | 14.0 | 32.00 | 13 | NaN | ... | 41.00 | NaN | Санкт-Петербург | 31856.0 | 8098.0 | 2.0 | 112.0 | 1.0 | 48.0 | 121.0 |
| 5 | 10 | 2890000.0 | 30.40 | 2018-09-10T00:00:00 | 1 | NaN | 12.0 | 14.40 | 5 | NaN | ... | 9.10 | NaN | городской посёлок Янино-1 | NaN | NaN | NaN | NaN | NaN | NaN | 55.0 |
| 6 | 6 | 3700000.0 | 37.30 | 2017-11-02T00:00:00 | 1 | NaN | 26.0 | 10.60 | 6 | NaN | ... | 14.40 | 1.0 | посёлок Парголово | 52996.0 | 19143.0 | 0.0 | NaN | 0.0 | NaN | 155.0 |
| 7 | 5 | 7915000.0 | 71.60 | 2019-04-18T00:00:00 | 2 | NaN | 24.0 | NaN | 22 | NaN | ... | 18.90 | 2.0 | Санкт-Петербург | 23982.0 | 11634.0 | 0.0 | NaN | 0.0 | NaN | NaN |
| 8 | 20 | 2900000.0 | 33.16 | 2018-05-23T00:00:00 | 1 | NaN | 27.0 | 15.43 | 26 | NaN | ... | 8.81 | NaN | посёлок Мурино | NaN | NaN | NaN | NaN | NaN | NaN | 189.0 |
| 9 | 18 | 5400000.0 | 61.00 | 2017-02-26T00:00:00 | 3 | 2.50 | 9.0 | 43.60 | 7 | NaN | ... | 6.50 | 2.0 | Санкт-Петербург | 50898.0 | 15008.0 | 0.0 | NaN | 0.0 | NaN | 289.0 |
| 10 | 5 | 5050000.0 | 39.60 | 2017-11-16T00:00:00 | 1 | 2.67 | 12.0 | 20.30 | 3 | NaN | ... | 8.50 | NaN | Санкт-Петербург | 38357.0 | 13878.0 | 1.0 | 310.0 | 2.0 | 553.0 | 137.0 |
| 11 | 9 | 3300000.0 | 44.00 | 2018-08-27T00:00:00 | 2 | NaN | 5.0 | 31.00 | 4 | False | ... | 6.00 | 1.0 | Ломоносов | 48252.0 | 51677.0 | 0.0 | NaN | 0.0 | NaN | 7.0 |
| 12 | 10 | 3890000.0 | 54.00 | 2016-06-30T00:00:00 | 2 | NaN | 5.0 | 30.00 | 5 | NaN | ... | 9.00 | 0.0 | Сертолово | NaN | NaN | NaN | NaN | NaN | NaN | 90.0 |
| 13 | 20 | 3550000.0 | 42.80 | 2017-07-01T00:00:00 | 2 | 2.56 | 5.0 | 27.00 | 5 | NaN | ... | 5.20 | 1.0 | Петергоф | 37868.0 | 33058.0 | 1.0 | 294.0 | 3.0 | 298.0 | 366.0 |
| 14 | 1 | 4400000.0 | 36.00 | 2016-06-23T00:00:00 | 1 | NaN | 6.0 | 17.00 | 1 | NaN | ... | 8.00 | 0.0 | Пушкин | 20782.0 | 30759.0 | 0.0 | NaN | 1.0 | 96.0 | 203.0 |
| 15 | 16 | 4650000.0 | 39.00 | 2017-11-18T00:00:00 | 1 | NaN | 14.0 | 20.50 | 5 | NaN | ... | 7.60 | 1.0 | Санкт-Петербург | 12900.0 | 14259.0 | 1.0 | 590.0 | 1.0 | 296.0 | 19.0 |
| 16 | 11 | 6700000.0 | 82.00 | 2017-11-23T00:00:00 | 3 | 3.05 | 5.0 | 55.60 | 1 | NaN | ... | 9.00 | NaN | Санкт-Петербург | 22108.0 | 10698.0 | 3.0 | 420.0 | 0.0 | NaN | 397.0 |
| 17 | 6 | 4180000.0 | 36.00 | 2016-09-09T00:00:00 | 1 | NaN | 17.0 | 16.50 | 7 | NaN | ... | 11.00 | 1.0 | Санкт-Петербург | 33564.0 | 14616.0 | 0.0 | NaN | 1.0 | 859.0 | 571.0 |
| 18 | 8 | 3250000.0 | 31.00 | 2017-01-27T00:00:00 | 1 | 2.50 | 5.0 | 19.40 | 2 | NaN | ... | 5.60 | 1.0 | Санкт-Петербург | 44060.0 | 10842.0 | 1.0 | 759.0 | 0.0 | NaN | 168.0 |
| 19 | 16 | 14200000.0 | 121.00 | 2019-01-09T00:00:00 | 3 | 2.75 | 16.0 | 76.00 | 8 | NaN | ... | 12.00 | NaN | Санкт-Петербург | 38900.0 | 12843.0 | 0.0 | NaN | 0.0 | NaN | 97.0 |
| 20 | 12 | 6120000.0 | 80.00 | 2017-09-28T00:00:00 | 3 | 2.70 | 27.0 | 48.00 | 11 | NaN | ... | 12.00 | 2.0 | посёлок Парголово | 53134.0 | 19311.0 | 0.0 | NaN | 0.0 | NaN | 74.0 |
| 21 | 13 | 3200000.0 | 31.60 | 2018-03-14T00:00:00 | 1 | NaN | 5.0 | 16.90 | 2 | NaN | ... | 5.70 | 1.0 | Санкт-Петербург | 36064.0 | 9722.0 | 1.0 | 248.0 | 0.0 | NaN | 310.0 |
| 22 | 20 | 5000000.0 | 58.00 | 2017-04-24T00:00:00 | 2 | 2.75 | 25.0 | 30.00 | 15 | NaN | ... | 11.00 | 2.0 | деревня Кудрово | NaN | NaN | NaN | NaN | NaN | NaN | 60.0 |
| 23 | 11 | 2950000.0 | 32.00 | 2016-10-29T00:00:00 | 1 | 2.60 | 9.0 | 17.70 | 9 | NaN | ... | 6.10 | NaN | Санкт-Петербург | 15414.0 | 14211.0 | 2.0 | 517.0 | 1.0 | 190.0 | 615.0 |
| 24 | 8 | 6500000.0 | 97.20 | 2015-10-31T00:00:00 | 2 | NaN | 3.0 | 46.50 | 1 | NaN | ... | 19.60 | 0.0 | Санкт-Петербург | 20052.0 | 2336.0 | 3.0 | 411.0 | 3.0 | 124.0 | 265.0 |
| 25 | 3 | 6800000.0 | 76.00 | 2015-10-01T00:00:00 | 2 | 2.75 | 23.0 | 39.00 | 18 | False | ... | 15.00 | 2.0 | Санкт-Петербург | 34967.0 | 14640.0 | 0.0 | NaN | 1.0 | 564.0 | 300.0 |
| 26 | 6 | 4050000.0 | 60.00 | 2017-04-28T00:00:00 | 4 | NaN | 5.0 | 43.00 | 4 | NaN | ... | 7.00 | NaN | Санкт-Петербург | 11580.0 | 10510.0 | 0.0 | NaN | 1.0 | 95.0 | 265.0 |
| 27 | 20 | 7100000.0 | 70.00 | 2017-05-12T00:00:00 | 3 | 2.60 | 17.0 | 49.00 | 11 | NaN | ... | 9.20 | 0.0 | Санкт-Петербург | 29197.0 | 11456.0 | 2.0 | 452.0 | 1.0 | 779.0 | 151.0 |
| 28 | 8 | 4170000.0 | 44.00 | 2017-12-13T00:00:00 | 1 | 2.90 | 6.0 | 20.80 | 1 | NaN | ... | 11.70 | 1.0 | Пушкин | 18557.0 | 30710.0 | 0.0 | NaN | 1.0 | 118.0 | 74.0 |
| 29 | 9 | 8600000.0 | 100.00 | 2016-04-09T00:00:00 | 3 | NaN | 19.0 | 52.00 | 15 | False | ... | 11.00 | 0.0 | Санкт-Петербург | 30394.0 | 11526.0 | 1.0 | 532.0 | 1.0 | 896.0 | 125.0 |
30 rows × 22 columns
# Изучаем столбцы и их типы
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 23699 entries, 0 to 23698 Data columns (total 22 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23699 non-null int64 1 last_price 23699 non-null float64 2 total_area 23699 non-null float64 3 first_day_exposition 23699 non-null object 4 rooms 23699 non-null int64 5 ceiling_height 14504 non-null float64 6 floors_total 23613 non-null float64 7 living_area 21796 non-null float64 8 floor 23699 non-null int64 9 is_apartment 2775 non-null object 10 studio 23699 non-null bool 11 open_plan 23699 non-null bool 12 kitchen_area 21421 non-null float64 13 balcony 12180 non-null float64 14 locality_name 23650 non-null object 15 airports_nearest 18157 non-null float64 16 cityCenters_nearest 18180 non-null float64 17 parks_around3000 18181 non-null float64 18 parks_nearest 8079 non-null float64 19 ponds_around3000 18181 non-null float64 20 ponds_nearest 9110 non-null float64 21 days_exposition 20518 non-null float64 dtypes: bool(2), float64(14), int64(3), object(3) memory usage: 3.7+ MB
# Просмотр сводной статистики
data.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| total_images | 23699.0 | 9.858475e+00 | 5.682529e+00 | 0.0 | 6.00 | 9.00 | 14.0 | 50.0 |
| last_price | 23699.0 | 6.541549e+06 | 1.088701e+07 | 12190.0 | 3400000.00 | 4650000.00 | 6800000.0 | 763000000.0 |
| total_area | 23699.0 | 6.034865e+01 | 3.565408e+01 | 12.0 | 40.00 | 52.00 | 69.9 | 900.0 |
| rooms | 23699.0 | 2.070636e+00 | 1.078405e+00 | 0.0 | 1.00 | 2.00 | 3.0 | 19.0 |
| ceiling_height | 14504.0 | 2.771499e+00 | 1.261056e+00 | 1.0 | 2.52 | 2.65 | 2.8 | 100.0 |
| floors_total | 23613.0 | 1.067382e+01 | 6.597173e+00 | 1.0 | 5.00 | 9.00 | 16.0 | 60.0 |
| living_area | 21796.0 | 3.445785e+01 | 2.203045e+01 | 2.0 | 18.60 | 30.00 | 42.3 | 409.7 |
| floor | 23699.0 | 5.892358e+00 | 4.885249e+00 | 1.0 | 2.00 | 4.00 | 8.0 | 33.0 |
| kitchen_area | 21421.0 | 1.056981e+01 | 5.905438e+00 | 1.3 | 7.00 | 9.10 | 12.0 | 112.0 |
| balcony | 12180.0 | 1.150082e+00 | 1.071300e+00 | 0.0 | 0.00 | 1.00 | 2.0 | 5.0 |
| airports_nearest | 18157.0 | 2.879367e+04 | 1.263088e+04 | 0.0 | 18585.00 | 26726.00 | 37273.0 | 84869.0 |
| cityCenters_nearest | 18180.0 | 1.419128e+04 | 8.608386e+03 | 181.0 | 9238.00 | 13098.50 | 16293.0 | 65968.0 |
| parks_around3000 | 18181.0 | 6.114075e-01 | 8.020736e-01 | 0.0 | 0.00 | 0.00 | 1.0 | 3.0 |
| parks_nearest | 8079.0 | 4.908046e+02 | 3.423180e+02 | 1.0 | 288.00 | 455.00 | 612.0 | 3190.0 |
| ponds_around3000 | 18181.0 | 7.702547e-01 | 9.383456e-01 | 0.0 | 0.00 | 1.00 | 1.0 | 3.0 |
| ponds_nearest | 9110.0 | 5.179809e+02 | 2.777206e+02 | 13.0 | 294.00 | 502.00 | 729.0 | 1344.0 |
| days_exposition | 20518.0 | 1.808886e+02 | 2.197280e+02 | 1.0 | 45.00 | 95.00 | 232.0 | 1580.0 |
# Проверим наличие пропусков в каждом столбце(%)
pd.DataFrame(round(data.isna().mean()*100,)).style.background_gradient('BuPu')
| 0 | |
|---|---|
| total_images | 0.000000 |
| last_price | 0.000000 |
| total_area | 0.000000 |
| first_day_exposition | 0.000000 |
| rooms | 0.000000 |
| ceiling_height | 39.000000 |
| floors_total | 0.000000 |
| living_area | 8.000000 |
| floor | 0.000000 |
| is_apartment | 88.000000 |
| studio | 0.000000 |
| open_plan | 0.000000 |
| kitchen_area | 10.000000 |
| balcony | 49.000000 |
| locality_name | 0.000000 |
| airports_nearest | 23.000000 |
| cityCenters_nearest | 23.000000 |
| parks_around3000 | 23.000000 |
| parks_nearest | 66.000000 |
| ponds_around3000 | 23.000000 |
| ponds_nearest | 62.000000 |
| days_exposition | 13.000000 |
# пропущенные значения бары
def pass_value_barh(df):
try:
(
(df.isna().mean()*100)
.to_frame()
.rename(columns = {0:'space'})
.query('space > 0')
.sort_values(by = 'space', ascending = True)
.plot(kind= 'barh', figsize=(19,6), rot = -5, legend = False, fontsize = 16)
.set_title('Пример' + "\n", fontsize = 22, color = 'SteelBlue')
);
except:
print('пропусков не осталось :) ')
pass_value_barh(data)
# Постоим гистограммы для каждой колонки
params = {'axes.titlesize':'18',
'xtick.labelsize':'14',
'ytick.labelsize':'14'}
#matplotlib.rcParams.update(params)
data.hist(figsize=(25, 20), color='orange')
plt.show()
ВЫВОД:
В данном разделе нам предстоит сделать следующие шаги:
> тип столбца => преобразуем в нужный тип
> пустые значения => просавляем "0"/находим median или mean/ удаляем
> уникальные значения => корректное написание значений/ явные/неявные дубликаты
> аномалии => восстанавливаем корректное значение/удаляем редкие и выбивающие значения
Без проведения очистки данных будет сложно провести дополнительный анализ.
Чтобы преобразовать типы данных в pandas, используем способы:
first_day_exposition¶Тип колонки object меняем на datetime\, чтобы мы смогли определить день недели, месяц/год публикайии, срок продажи квартиры(добавим столбец day_exposition), убрали мин/сек, тк они не отражены.
data['first_day_exposition'] = pd.to_datetime(data['first_day_exposition'])
# Проверяем пустые значения
data['first_day_exposition'].isna().sum()
0
is_apartment¶Тип колонки object на bool- булевое значение, Nan изменили на False, тк обратное не указано в таблице.
data['is_apartment'] = data['is_apartment'].fillna(False).astype('bool')
balcony¶Тип колонки float64 на int64 - целое число и запомняем пустое значение на 0, тк продавец не указал обратное.
data['balcony'] = data['balcony'].fillna(0).astype('int')
data['balcony'].value_counts()
0 15277 1 4195 2 3659 5 304 4 183 3 81 Name: balcony, dtype: int64
floors_total¶Тип колонки float64 на int64 - целое число и пустые значения заполняем медианной.
Пустых значений- менее 1%(незначительно) или 86 значений.\ Несколько вариантов:
Вывод: Удаляем и меняем тип колонки float64 на int64.
Общее количество этажей в доме по имеющимся данным узнать нельзя. Средним значением заполнять нет смысла, т.к. значение floor может оказаться больше floors_total. Логичнее эти данные удалить.
# Удалим пропуски
total_floor_isna = data['floors_total'].isna()
data[total_floor_isna]
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | kitchen_area | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 186 | 12 | 11640000.0 | 65.2 | 2018-10-02 | 2 | NaN | NaN | 30.80 | 4 | False | ... | 12.00 | 0 | Санкт-Петербург | 39197.0 | 12373.0 | 1.0 | 123.0 | 0.0 | NaN | 49.0 |
| 237 | 4 | 2438033.0 | 28.1 | 2016-11-23 | 1 | NaN | NaN | 20.75 | 1 | False | ... | NaN | 0 | Санкт-Петербург | 22041.0 | 17369.0 | 0.0 | NaN | 1.0 | 374.0 | 251.0 |
| 457 | 4 | 9788348.0 | 70.8 | 2015-08-01 | 2 | NaN | NaN | 38.40 | 12 | False | ... | 10.63 | 0 | Санкт-Петербург | 37364.0 | 8322.0 | 2.0 | 309.0 | 2.0 | 706.0 | 727.0 |
| 671 | 4 | 6051191.0 | 93.6 | 2017-04-06 | 3 | NaN | NaN | 47.10 | 8 | False | ... | 16.80 | 0 | Санкт-Петербург | 22041.0 | 17369.0 | 0.0 | NaN | 1.0 | 374.0 | 123.0 |
| 1757 | 5 | 3600000.0 | 39.0 | 2017-04-22 | 1 | NaN | NaN | NaN | 9 | False | ... | NaN | 0 | Санкт-Петербург | 22735.0 | 11618.0 | 1.0 | 835.0 | 1.0 | 652.0 | 77.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 22542 | 5 | 8500000.0 | 63.5 | 2017-05-24 | 2 | 2.8 | NaN | NaN | 3 | False | ... | NaN | 0 | Санкт-Петербург | 51340.0 | 15363.0 | 0.0 | NaN | 1.0 | 853.0 | 512.0 |
| 22656 | 4 | 4574160.0 | 64.5 | 2017-04-02 | 2 | NaN | NaN | 31.70 | 20 | False | ... | 14.40 | 0 | Санкт-Петербург | 22041.0 | 17369.0 | 0.0 | NaN | 1.0 | 374.0 | 127.0 |
| 22808 | 0 | 14569263.0 | 110.4 | 2016-11-20 | 3 | NaN | NaN | 45.38 | 6 | False | ... | 23.42 | 0 | Санкт-Петербург | 19095.0 | 4529.0 | 0.0 | NaN | 0.0 | NaN | 260.0 |
| 23590 | 0 | 21187872.0 | 123.3 | 2017-04-25 | 3 | NaN | NaN | 50.40 | 18 | False | ... | 23.60 | 0 | Санкт-Петербург | 19095.0 | 4529.0 | 0.0 | NaN | 0.0 | NaN | 104.0 |
| 23658 | 6 | 3063600.0 | 43.8 | 2016-11-28 | 1 | 2.7 | NaN | 14.00 | 8 | False | ... | 15.50 | 2 | Санкт-Петербург | 8426.0 | 12082.0 | 2.0 | 24.0 | 1.0 | 271.0 | 246.0 |
86 rows × 22 columns
# Удалим экстремумы
data = data[(data['floors_total'] < 40)]
# согласно статистике, в Спб нет домов выше 40 этажей
# Заменим тип колонки float64 на int64.
data['floors_total'] = data['floors_total'].astype('int')
# Проверим выбросы
data.floors_total.value_counts().to_frame()
| floors_total | |
|---|---|
| 5 | 5788 |
| 9 | 3761 |
| 16 | 1376 |
| 12 | 1362 |
| 4 | 1200 |
| 10 | 1174 |
| 25 | 1075 |
| 6 | 914 |
| 17 | 833 |
| 3 | 668 |
| 7 | 592 |
| 14 | 553 |
| 18 | 505 |
| 24 | 469 |
| 8 | 390 |
| 2 | 383 |
| 15 | 365 |
| 23 | 352 |
| 19 | 339 |
| 22 | 286 |
| 20 | 271 |
| 13 | 229 |
| 11 | 203 |
| 27 | 164 |
| 21 | 158 |
| 26 | 124 |
| 1 | 25 |
| 35 | 24 |
| 28 | 21 |
| 36 | 3 |
| 34 | 1 |
| 29 | 1 |
| 33 | 1 |
| 37 | 1 |
# Проверим нет ли этаже выше этажности дома
check_floor = data['floors_total'] < data['floor']
data.loc[check_floor]
# Таких этажей нет, значит уловие по пропускам оставим
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | kitchen_area | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition |
|---|
0 rows × 22 columns
# check
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 23611 entries, 0 to 23698 Data columns (total 22 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23611 non-null int64 1 last_price 23611 non-null float64 2 total_area 23611 non-null float64 3 first_day_exposition 23611 non-null datetime64[ns] 4 rooms 23611 non-null int64 5 ceiling_height 14493 non-null float64 6 floors_total 23611 non-null int64 7 living_area 21741 non-null float64 8 floor 23611 non-null int64 9 is_apartment 23611 non-null bool 10 studio 23611 non-null bool 11 open_plan 23611 non-null bool 12 kitchen_area 21380 non-null float64 13 balcony 23611 non-null int64 14 locality_name 23563 non-null object 15 airports_nearest 18079 non-null float64 16 cityCenters_nearest 18102 non-null float64 17 parks_around3000 18103 non-null float64 18 parks_nearest 8043 non-null float64 19 ponds_around3000 18103 non-null float64 20 ponds_nearest 9060 non-null float64 21 days_exposition 20439 non-null float64 dtypes: bool(3), datetime64[ns](1), float64(12), int64(5), object(1) memory usage: 3.7+ MB
# check
data.shape
(23611, 22)
# Прверим статистические показатели
data['floors_total'].describe()
count 23611.000000 mean 10.669984 std 6.584146 min 1.000000 25% 5.000000 50% 9.000000 75% 16.000000 max 37.000000 Name: floors_total, dtype: float64
check_floor = data['floors_total'] < data['floor']
data.loc[check_floor]
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | kitchen_area | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition |
|---|
0 rows × 22 columns
data['floors_total'].isna().sum()
0
days_exposition¶Пустых значений- 13%(значительно).\ Несколько вариантов:
Вывод: меняем тип колонки float64 на int64 и пустые значения меняем на 0(означает, что квартира еще не продана). Кроме этого, оставляем выбросы, тк они могут быть подтверждением долгих продаж.
# Меняем тип колонки float64 на int64 и исключаем 0
data['days_exposition'] = data['days_exposition'].fillna(0).astype('int')
# Проверяем уникальные значения
data['days_exposition'].unique()
array([ 0, 81, 558, ..., 1360, 1007, 971])
# Проверим пустые значения
data['days_exposition'].isna().sum()
0
#Удаляем выбросы, те 0, тк они показывают, что квартиры еще не проданы
#data = data[(data['days_exposition'] > 0)]
# Используем метод describe() для получение статистических значений
data['days_exposition'].describe()
count 23611.000000 mean 156.590403 std 213.510349 min 0.000000 25% 22.000000 50% 73.000000 75% 199.000000 max 1580.000000 Name: days_exposition, dtype: float64
# Проверим выбросы
data.days_exposition.value_counts().to_frame()
| days_exposition | |
|---|---|
| 0 | 3172 |
| 45 | 879 |
| 60 | 538 |
| 7 | 234 |
| 30 | 208 |
| ... | ... |
| 1417 | 1 |
| 1292 | 1 |
| 1325 | 1 |
| 1345 | 1 |
| 971 | 1 |
1140 rows × 1 columns
locality_name¶Пустых значений- менее 1%(незначительно). Есть неявные дубликаты.\ Несколько вариантов:
# Выводим на экран все значения
# Корректируем правильное написание значений
data['locality_name'] = data['locality_name'].str.replace('ё', 'е')
data['locality_name'] = data['locality_name'].str.replace('городской поселок', 'поселок городского типа')
# pd.set_option('display.max_rows', None)
data.groupby(['locality_name'])['locality_name'].count()
locality_name
Бокситогорск 16
Волосово 36
Волхов 111
Всеволожск 398
Выборг 237
...
село Путилово 2
село Рождествено 3
село Русско-Высоцкое 9
село Старая Ладога 2
село Шум 1
Name: locality_name, Length: 323, dtype: int64
#Удаляем строки, в которых нет значений. Без этих данные крайне сложно анализировать.
data = data.dropna(subset = ['locality_name'])
#Проверяем наличие пустых значений
data['locality_name'].isna().sum()
0
# check
data['locality_name'].nunique()
323
# check
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 23563 entries, 0 to 23698 Data columns (total 22 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23563 non-null int64 1 last_price 23563 non-null float64 2 total_area 23563 non-null float64 3 first_day_exposition 23563 non-null datetime64[ns] 4 rooms 23563 non-null int64 5 ceiling_height 14479 non-null float64 6 floors_total 23563 non-null int64 7 living_area 21698 non-null float64 8 floor 23563 non-null int64 9 is_apartment 23563 non-null bool 10 studio 23563 non-null bool 11 open_plan 23563 non-null bool 12 kitchen_area 21341 non-null float64 13 balcony 23563 non-null int64 14 locality_name 23563 non-null object 15 airports_nearest 18039 non-null float64 16 cityCenters_nearest 18062 non-null float64 17 parks_around3000 18063 non-null float64 18 parks_nearest 8028 non-null float64 19 ponds_around3000 18063 non-null float64 20 ponds_nearest 9035 non-null float64 21 days_exposition 23563 non-null int64 dtypes: bool(3), datetime64[ns](1), float64(11), int64(6), object(1) memory usage: 3.7+ MB
living_area¶Пустых значений- 8%(незначительно). Можно предположить, что жилая площадь имеет зависимость от количества комнат в квартире.
#Проверим это, рассчитав коэффициент корреляции Пирсона.
data['rooms'].corr(data['living_area'])
0.8461104920850049
Для более точетного попадания медианного значения в пустые значения можно разбить на категории общие площади:
def total_area_split(area):
try:
if 0 <= area <= 30:
return 'A'
elif 31 < area <= 50:
return 'B'
elif 51 < area <= 80:
return 'C'
elif 81 < area <= 110:
return 'D'
elif 111 < area <= 200:
return 'E'
elif area > 201:
return 'F'
except:
pass
data = data.reset_index(drop=True)
data['total_area_split'] = data['total_area'].apply(total_area_split)
# Делаем замену пустых значений медианными по каждой категории
data.pivot_table(index='total_area_split', values = 'living_area', aggfunc = 'median').reset_index()
| total_area_split | living_area | |
|---|---|---|
| 0 | A | 16.20 |
| 1 | B | 19.00 |
| 2 | C | 38.00 |
| 3 | D | 53.85 |
| 4 | E | 79.34 |
| 5 | F | 160.00 |
# Заменим пропуски на среднюю площадь кухни в разрезе категорий
for i in data['total_area_split'].unique():
data.loc[(data['total_area_split'] == i) & (data['living_area'].isna()), 'kitchen_area'] = \
data.loc[(data['total_area_split'] == i), 'living_area'].median()
# Проверим отклонения
data.query('total_area < living_area')
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split |
|---|
0 rows × 23 columns
# check
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 23563 entries, 0 to 23562 Data columns (total 23 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23563 non-null int64 1 last_price 23563 non-null float64 2 total_area 23563 non-null float64 3 first_day_exposition 23563 non-null datetime64[ns] 4 rooms 23563 non-null int64 5 ceiling_height 14479 non-null float64 6 floors_total 23563 non-null int64 7 living_area 21698 non-null float64 8 floor 23563 non-null int64 9 is_apartment 23563 non-null bool 10 studio 23563 non-null bool 11 open_plan 23563 non-null bool 12 kitchen_area 22705 non-null float64 13 balcony 23563 non-null int64 14 locality_name 23563 non-null object 15 airports_nearest 18039 non-null float64 16 cityCenters_nearest 18062 non-null float64 17 parks_around3000 18063 non-null float64 18 parks_nearest 8028 non-null float64 19 ponds_around3000 18063 non-null float64 20 ponds_nearest 9035 non-null float64 21 days_exposition 23563 non-null int64 22 total_area_split 22463 non-null object dtypes: bool(3), datetime64[ns](1), float64(11), int64(6), object(2) memory usage: 3.7+ MB
# В результате замены пропусков у нас получилось несколько строк, где жилая площадь больше общей, что в принципе невозможно.
# Удалим эти аномалии
data = data.query('total_area > living_area | living_area.isna()')
# check
data.shape
(23561, 23)
#Удаляем выбросы
data = data[(data ['living_area'] <= 220) | (data ['living_area'].isna()) ]
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 23536 entries, 0 to 23562 Data columns (total 23 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23536 non-null int64 1 last_price 23536 non-null float64 2 total_area 23536 non-null float64 3 first_day_exposition 23536 non-null datetime64[ns] 4 rooms 23536 non-null int64 5 ceiling_height 14460 non-null float64 6 floors_total 23536 non-null int64 7 living_area 21671 non-null float64 8 floor 23536 non-null int64 9 is_apartment 23536 non-null bool 10 studio 23536 non-null bool 11 open_plan 23536 non-null bool 12 kitchen_area 22681 non-null float64 13 balcony 23536 non-null int64 14 locality_name 23536 non-null object 15 airports_nearest 18015 non-null float64 16 cityCenters_nearest 18036 non-null float64 17 parks_around3000 18037 non-null float64 18 parks_nearest 8010 non-null float64 19 ponds_around3000 18037 non-null float64 20 ponds_nearest 9018 non-null float64 21 days_exposition 23536 non-null int64 22 total_area_split 22436 non-null object dtypes: bool(3), datetime64[ns](1), float64(11), int64(6), object(2) memory usage: 3.8+ MB
# Проверим выбросы
data.living_area.value_counts().to_frame()
| living_area | |
|---|---|
| 18.00 | 882 |
| 17.00 | 673 |
| 30.00 | 598 |
| 16.00 | 483 |
| 20.00 | 479 |
| ... | ... |
| 28.86 | 1 |
| 76.70 | 1 |
| 124.90 | 1 |
| 36.07 | 1 |
| 42.55 | 1 |
1748 rows × 1 columns
data['living_area'].isna().sum()
1865
data['living_area'].describe()
count 21671.000000 mean 34.174322 std 20.353655 min 2.000000 25% 18.600000 50% 30.000000 75% 42.200000 max 220.000000 Name: living_area, dtype: float64
kitchen_area¶Пустых значений- 10%(незначительно).
# Заменим на 0 если тип квартиры студио
data.loc[data['studio'] == True, 'kitchen_area'] = data.loc[data['studio'] == True, 'kitchen_area'].fillna(0)
# Проверяем, есть ли у студий кухни. Нет.
data.groupby('studio')['kitchen_area'].mean()
studio False 12.413214 True 0.554795 Name: kitchen_area, dtype: float64
# Заменим пропуски на среднюю площадь кухни в разрезе категорий
for i in data['total_area_split'].unique():
data.loc[(data['total_area_split'] == i) & (data['kitchen_area'].isna()), 'kitchen_area'] = \
data.loc[(data['total_area_split'] == i), 'kitchen_area'].median()
#Удаляем выбросы
data = data[(data ['kitchen_area'] < 55) | (data ['kitchen_area'].isna()) ]
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 23373 entries, 0 to 23562 Data columns (total 23 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23373 non-null int64 1 last_price 23373 non-null float64 2 total_area 23373 non-null float64 3 first_day_exposition 23373 non-null datetime64[ns] 4 rooms 23373 non-null int64 5 ceiling_height 14372 non-null float64 6 floors_total 23373 non-null int64 7 living_area 21643 non-null float64 8 floor 23373 non-null int64 9 is_apartment 23373 non-null bool 10 studio 23373 non-null bool 11 open_plan 23373 non-null bool 12 kitchen_area 23289 non-null float64 13 balcony 23373 non-null int64 14 locality_name 23373 non-null object 15 airports_nearest 17860 non-null float64 16 cityCenters_nearest 17880 non-null float64 17 parks_around3000 17881 non-null float64 18 parks_nearest 7913 non-null float64 19 ponds_around3000 17881 non-null float64 20 ponds_nearest 8918 non-null float64 21 days_exposition 23373 non-null int64 22 total_area_split 22273 non-null object dtypes: bool(3), datetime64[ns](1), float64(11), int64(6), object(2) memory usage: 3.8+ MB
# Используем метод describe() для получение статистических значений
data['kitchen_area'].describe()
count 23289.000000 mean 11.698594 std 7.876613 min 0.000000 25% 7.100000 50% 9.500000 75% 12.800000 max 54.300000 Name: kitchen_area, dtype: float64
# Проверим пустые значения
data['kitchen_area'].isna().sum()
84
# НЕПРАВИЛЬНО Проверим
check_living_area = data['total_area']*0.9 <= (data['living_area'] + data['kitchen_area'])
data.loc[check_living_area]
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 41 | 16 | 7900000.0 | 74.0 | 2016-05-04 | 3 | NaN | 14 | 59.0 | 8 | False | ... | 5 | Санкт-Петербург | 37715.0 | 12612.0 | 1.0 | 725.0 | 1.0 | 759.0 | 179 | C |
| 171 | 6 | 3800000.0 | 49.0 | 2019-02-22 | 2 | 2.50 | 9 | 38.0 | 8 | False | ... | 1 | поселок Шушары | 18471.0 | 24272.0 | 0.0 | NaN | 0.0 | NaN | 11 | B |
| 184 | 7 | 2600000.0 | 30.2 | 2018-02-14 | 1 | 2.65 | 9 | 26.1 | 5 | False | ... | 0 | Санкт-Петербург | 13952.0 | 15031.0 | 0.0 | NaN | 1.0 | 824.0 | 12 | None |
| 219 | 8 | 3890000.0 | 37.0 | 2018-07-09 | 1 | 2.50 | 25 | 28.0 | 6 | False | ... | 0 | Санкт-Петербург | 25408.0 | 16166.0 | 0.0 | NaN | 0.0 | NaN | 28 | B |
| 317 | 19 | 4850000.0 | 59.7 | 2015-03-19 | 3 | 2.50 | 5 | 47.4 | 4 | False | ... | 1 | Санкт-Петербург | 43095.0 | 15599.0 | 0.0 | NaN | 0.0 | NaN | 974 | C |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 23163 | 10 | 1850000.0 | 48.2 | 2019-03-13 | 2 | NaN | 5 | 42.2 | 2 | False | ... | 1 | Высоцк | NaN | NaN | NaN | NaN | NaN | NaN | 45 | B |
| 23194 | 12 | 4299000.0 | 54.0 | 2016-11-25 | 2 | NaN | 14 | 40.0 | 8 | False | ... | 0 | Колпино | 25612.0 | 31399.0 | 0.0 | NaN | 0.0 | NaN | 343 | C |
| 23387 | 13 | 8500000.0 | 65.0 | 2018-11-01 | 2 | NaN | 4 | 44.0 | 2 | False | ... | 0 | Санкт-Петербург | 22276.0 | 3996.0 | 1.0 | 627.0 | 0.0 | NaN | 0 | C |
| 23445 | 7 | 2350000.0 | 24.8 | 2018-06-07 | 1 | NaN | 10 | 18.0 | 2 | False | ... | 0 | Кудрово | NaN | NaN | NaN | NaN | NaN | NaN | 89 | A |
| 23544 | 14 | 5000000.0 | 59.0 | 2019-04-04 | 3 | 2.50 | 5 | 52.0 | 1 | False | ... | 0 | Санкт-Петербург | 26835.0 | 11878.0 | 0.0 | NaN | 0.0 | NaN | 0 | C |
640 rows × 23 columns
data.shape
(23373, 23)
airports_nearest¶Пустых значений- 23%(значительно), если решим изменить на '0, то повлияет на статистичекие показатели.\
Вывод: Оставляем, как есть, тк многие населенные пункты могут и не иметь аэропорт.
# Проверим пустые значения
data['airports_nearest'].isna().sum()
5513
# Используем метод describe() для получение статистических значений
data['airports_nearest'].describe()
count 17860.000000 mean 28791.766461 std 12658.084722 min 0.000000 25% 18503.000000 50% 26765.500000 75% 37310.750000 max 84869.000000 Name: airports_nearest, dtype: float64
ceiling_height¶Пустых значений- 39%(значительно), если решим изменить на '0, то повлияет на статистичекие показатели.\ Несколько вариантов:
если пустое значение в доме с такой же этажностью, заполняем медианным значением высоты потолка этой этажности, тк данные выглядят реалистично.
Вывод: Выполняем условия и заполняем пропуски.
# Проверим уникальные значения
data['ceiling_height'].unique()
array([ 2.7 , nan, 3.03, 2.5 , 2.67, 2.56, 3.05, 2.75,
2.6 , 2.9 , 2.8 , 2.55, 3. , 2.65, 3.2 , 2.61,
3.25, 3.45, 2.77, 2.85, 2.64, 2.57, 4.15, 3.5 ,
3.3 , 2.71, 4. , 2.47, 2.73, 2.84, 3.1 , 2.34,
3.4 , 3.06, 2.72, 2.54, 2.51, 2.78, 2.76, 25. ,
2.58, 3.7 , 2.52, 5.2 , 2.87, 2.66, 2.59, 2. ,
2.45, 3.6 , 2.92, 3.11, 3.13, 3.8 , 3.15, 3.55,
3.62, 3.12, 2.53, 2.96, 2.46, 2.74, 5. , 2.79,
2.95, 4.06, 2.94, 3.82, 3.54, 3.53, 2.83, 4.7 ,
2.4 , 3.38, 3.01, 3.65, 3.18, 3.35, 2.3 , 3.57,
2.48, 2.62, 2.82, 3.98, 2.63, 3.83, 3.52, 3.95,
3.75, 3.67, 3.87, 3.66, 3.85, 4.19, 3.24, 4.8 ,
4.5 , 4.2 , 3.36, 3.86, 32. , 3.68, 3.07, 3.37,
3.09, 8. , 3.16, 3.26, 3.34, 2.81, 3.44, 2.97,
3.14, 4.37, 2.68, 3.9 , 3.22, 3.27, 27. , 4.1 ,
2.93, 3.46, 24. , 3.47, 3.33, 3.63, 3.32, 26. ,
1.2 , 8.3 , 2.98, 2.86, 2.88, 3.08, 3.17, 4.4 ,
3.28, 3.04, 4.45, 5.5 , 3.84, 3.23, 3.02, 3.21,
3.43, 3.78, 4.3 , 3.39, 2.69, 3.31, 5.3 , 3.56,
2.2 , 3.93, 3.42, 2.99, 3.49, 14. , 2.91, 3.88,
1.75, 4.25, 3.29, 20. , 3.51, 2.25, 3.76, 6. ,
22.6 , 2.89, 3.58, 5.8 , 27.5 , 2.49, 4.9 , 3.48,
10.3 , 1. , 100. , 3.59])
# За максимыльную высоту потолков возьмем 10, тк выше потолки встречаются крайне редко.
# Задача все экстемумы выше 10 привести к реальным значениям.
for ceiling in data['ceiling_height']:
if ceiling >=24:
ceiling = ceiling/10
# Фильтрация
data = data.loc[(data['ceiling_height'].isna()) | (data['ceiling_height']>= 2.4)\
& (data['ceiling_height']<=7)]
# Определим объекты, которые находятся в центре Спб и дальше.
def distance_from_center(distance):
try:
if 0 <= distance <= 7000:
return 'center_spb'
elif 7 < distance:
return 'no_center_spb'
except:
pass
# Далее создадим новый столбец 'from_center'
data = data.reset_index(drop=True)
data['from_center'] = data['cityCenters_nearest'].apply(distance_from_center)
# Заполняем пустые значение средним значением в разрезе удаленности от центра. Используем среднюю, тк выбросов нет.
data.pivot_table(index = 'from_center', values = 'ceiling_height', aggfunc = 'mean').reset_index()
for i in data['from_center'].unique():
data.loc[(data['from_center'] == i) & (data['ceiling_height'].isna()), 'ceiling_height'] = \
data.loc[(data['from_center'] == i), 'ceiling_height'].mean()
#Проверим пустые значения
data['ceiling_height'].isna().sum()
2556
# Проверим выбросы
data.ceiling_height.value_counts().to_frame()
| ceiling_height | |
|---|---|
| 2.664859 | 5361 |
| 2.500000 | 3513 |
| 2.600000 | 1645 |
| 2.700000 | 1569 |
| 3.102257 | 1084 |
| ... | ... |
| 4.450000 | 1 |
| 5.500000 | 1 |
| 3.840000 | 1 |
| 3.430000 | 1 |
| 3.590000 | 1 |
160 rows × 1 columns
# check
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 23323 entries, 0 to 23322 Data columns (total 24 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23323 non-null int64 1 last_price 23323 non-null float64 2 total_area 23323 non-null float64 3 first_day_exposition 23323 non-null datetime64[ns] 4 rooms 23323 non-null int64 5 ceiling_height 20767 non-null float64 6 floors_total 23323 non-null int64 7 living_area 21596 non-null float64 8 floor 23323 non-null int64 9 is_apartment 23323 non-null bool 10 studio 23323 non-null bool 11 open_plan 23323 non-null bool 12 kitchen_area 23239 non-null float64 13 balcony 23323 non-null int64 14 locality_name 23323 non-null object 15 airports_nearest 17835 non-null float64 16 cityCenters_nearest 17855 non-null float64 17 parks_around3000 17856 non-null float64 18 parks_nearest 7900 non-null float64 19 ponds_around3000 17856 non-null float64 20 ponds_nearest 8904 non-null float64 21 days_exposition 23323 non-null int64 22 total_area_split 22224 non-null object 23 from_center 17855 non-null object dtypes: bool(3), datetime64[ns](1), float64(11), int64(6), object(3) memory usage: 3.8+ MB
# check
data.shape
(23323, 24)
cityCenters_nearest¶Пустых значений- 23%(значительно), если решим изменить на '0, то повлияет на статистичекие показатели.\ Несколько вариантов:
если пустое значение в столбце cityCenters_nearest совпадает с 'locality_name' , заполняем медианным значением.
Вывод: После выполнения условия пустых значений осталось 20%, остальное ставляем, как есть и при расчетах их будем исключать.
Проверим распределение пропусков по городам
# Отфильтруем нужные ячейки с пустым расстоянием до центра города
cityCenters_nearest_nan = data[data.cityCenters_nearest.isna()]
cityCenters_nearest_nan.head()
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split | from_center | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 4 | 10 | 2890000.0 | 30.40 | 2018-09-10 | 1 | NaN | 12 | 14.40 | 5 | False | ... | поселок городского типа Янино-1 | NaN | NaN | NaN | NaN | NaN | NaN | 55 | None | None |
| 7 | 20 | 2900000.0 | 33.16 | 2018-05-23 | 1 | NaN | 27 | 15.43 | 26 | False | ... | поселок Мурино | NaN | NaN | NaN | NaN | NaN | NaN | 189 | B | None |
| 11 | 10 | 3890000.0 | 54.00 | 2016-06-30 | 2 | NaN | 5 | 30.00 | 5 | False | ... | Сертолово | NaN | NaN | NaN | NaN | NaN | NaN | 90 | C | None |
| 21 | 20 | 5000000.0 | 58.00 | 2017-04-24 | 2 | 2.75 | 25 | 30.00 | 15 | False | ... | деревня Кудрово | NaN | NaN | NaN | NaN | NaN | NaN | 60 | C | None |
| 29 | 12 | 2200000.0 | 32.80 | 2018-02-19 | 1 | NaN | 9 | NaN | 2 | False | ... | Коммунар | NaN | NaN | NaN | NaN | NaN | NaN | 63 | B | None |
5 rows × 24 columns
# Сгруппируем датафрейм с пропусками в расстояниях
locality_name_nan = cityCenters_nearest_nan.groupby('locality_name').count()
locality_name_nan
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | balcony | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split | from_center | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| locality_name | |||||||||||||||||||||
| Бокситогорск | 16 | 16 | 16 | 16 | 16 | 2 | 16 | 10 | 16 | 16 | ... | 16 | 0 | 0 | 0 | 0 | 0 | 0 | 16 | 15 | 0 |
| Волосово | 36 | 36 | 36 | 36 | 36 | 19 | 36 | 35 | 36 | 36 | ... | 36 | 0 | 0 | 0 | 0 | 0 | 0 | 36 | 35 | 0 |
| Волхов | 110 | 110 | 110 | 110 | 110 | 57 | 110 | 91 | 110 | 110 | ... | 110 | 0 | 0 | 0 | 0 | 0 | 0 | 110 | 106 | 0 |
| Всеволожск | 396 | 396 | 396 | 396 | 396 | 270 | 396 | 370 | 396 | 396 | ... | 396 | 0 | 0 | 0 | 0 | 0 | 0 | 396 | 380 | 0 |
| Выборг | 235 | 235 | 235 | 235 | 235 | 96 | 235 | 191 | 235 | 235 | ... | 235 | 0 | 0 | 0 | 0 | 0 | 0 | 235 | 219 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| село Путилово | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | ... | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 | 2 | 0 |
| село Рождествено | 3 | 3 | 3 | 3 | 3 | 0 | 3 | 3 | 3 | 3 | ... | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 0 |
| село Русско-Высоцкое | 9 | 9 | 9 | 9 | 9 | 5 | 9 | 8 | 9 | 9 | ... | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 9 | 0 |
| село Старая Ладога | 2 | 2 | 2 | 2 | 2 | 0 | 2 | 1 | 2 | 2 | ... | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 | 2 | 0 |
| село Шум | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
303 rows × 23 columns
# Пустых значений не много, ставлю загрушку
data['cityCenters_nearest'] = data['cityCenters_nearest'].fillna(-999)
#Проверяем наличие пустых значений
data['cityCenters_nearest'].isna().sum()
0
# Используем метод describe() для получение статистических значений
data['cityCenters_nearest'].describe()
count 23323.000000 mean 10668.691420 std 9907.138406 min -999.000000 25% 2140.500000 50% 11296.000000 75% 15255.000000 max 65968.000000 Name: cityCenters_nearest, dtype: float64
#check
x = (
data
.value_counts('locality_name')
.head(25)
.to_frame()
.rename(columns = {0:'count'})
)
y = (
data[data['airports_nearest'].isna()]
.value_counts('locality_name')).head(25).to_frame().rename(columns = {0:'count_gap'}
)
z = x.join(y, how = 'outer').reset_index().sort_values(by = 'locality_name')
z.style.format("{:,.0f}", subset = ['count_gap', 'count'])
| locality_name | count | count_gap | |
|---|---|---|---|
| 0 | Волхов | 110 | 110 |
| 1 | Всеволожск | 396 | 396 |
| 2 | Выборг | 235 | 235 |
| 3 | Гатчина | 306 | 306 |
| 4 | Кингисепп | 104 | 104 |
| 5 | Кириши | 125 | 125 |
| 6 | Кировск | nan | 84 |
| 7 | Колпино | 338 | nan |
| 8 | Коммунар | nan | 89 |
| 9 | Красное Село | 175 | nan |
| 10 | Кронштадт | 93 | nan |
| 11 | Кудрово | 170 | 170 |
| 12 | Ломоносов | 131 | nan |
| 13 | Луга | nan | 53 |
| 14 | Никольское | 93 | 93 |
| 15 | Отрадное | nan | 80 |
| 16 | Петергоф | 201 | nan |
| 17 | Приозерск | nan | 66 |
| 18 | Пушкин | 364 | nan |
| 19 | Санкт-Петербург | 15,455 | 76 |
| 20 | Сертолово | 142 | 142 |
| 21 | Сестрорецк | 180 | nan |
| 22 | Сланцы | 112 | 112 |
| 23 | Сосновый Бор | nan | 86 |
| 24 | Тосно | 103 | 103 |
| 25 | Шлиссельбург | nan | 57 |
| 26 | деревня Кудрово | 299 | 299 |
| 27 | деревня Новое Девяткино | 143 | 143 |
| 28 | деревня Старая | nan | 64 |
| 29 | поселок Бугры | 113 | 113 |
| 30 | поселок Мурино | 549 | 549 |
| 31 | поселок Парголово | 326 | nan |
| 32 | поселок Шушары | 439 | nan |
| 33 | поселок городского типа Янино-1 | nan | 68 |
parks_around3000¶Пустых значений- 23%(значительно). Вывод: Не трогаем
#Проверяем наличие пустых значений
data['parks_around3000'].isna().sum()
5467
# Используем метод describe() для получение статистических значений
data['parks_around3000'].describe()
count 17856.000000 mean 0.607191 std 0.799077 min 0.000000 25% 0.000000 50% 0.000000 75% 1.000000 max 3.000000 Name: parks_around3000, dtype: float64
floor¶Пустых значений- 0%. Тип колонки float64 меняемна int64 - целое число\ Анамалий нет.\ Вывод: Замена типа колонки на int64
#Проверяем наличие пустых значений
data['floor'].isna().sum()
0
data['floor'].unique()
array([ 8, 1, 4, 13, 5, 6, 22, 26, 7, 3, 2, 11, 15, 9, 18, 10, 19,
16, 20, 27, 25, 17, 14, 12, 21, 28, 24, 23, 30, 29, 32, 33, 31])
# Замена типа колонки на int64
data['floor'].astype('int')
0 8
1 1
2 4
3 13
4 5
..
23318 3
23319 4
23320 1
23321 12
23322 1
Name: floor, Length: 23323, dtype: int64
# Проверим выбросы
data.floor.value_counts().to_frame()
| floor | |
|---|---|
| 2 | 3312 |
| 3 | 3029 |
| 1 | 2885 |
| 4 | 2757 |
| 5 | 2580 |
| 6 | 1277 |
| 7 | 1199 |
| 8 | 1067 |
| 9 | 1037 |
| 10 | 680 |
| 12 | 515 |
| 11 | 515 |
| 13 | 371 |
| 15 | 338 |
| 14 | 329 |
| 16 | 313 |
| 17 | 223 |
| 18 | 174 |
| 19 | 141 |
| 21 | 119 |
| 22 | 111 |
| 20 | 108 |
| 23 | 98 |
| 24 | 60 |
| 25 | 45 |
| 26 | 24 |
| 27 | 10 |
| 28 | 1 |
| 30 | 1 |
| 29 | 1 |
| 32 | 1 |
| 33 | 1 |
| 31 | 1 |
total_area¶#Проверяем наличие пустых значений
data['total_area'].isna().sum()
0
#Удаляем выбросы
data = data[(data['total_area'] <= 200)]
# Проверим выбросы
data.total_area.value_counts().to_frame()
| total_area | |
|---|---|
| 45.00 | 415 |
| 42.00 | 381 |
| 31.00 | 346 |
| 60.00 | 345 |
| 44.00 | 342 |
| ... | ... |
| 149.60 | 1 |
| 46.19 | 1 |
| 150.61 | 1 |
| 111.80 | 1 |
| 76.75 | 1 |
1976 rows × 1 columns
# Используем метод describe() для получение статистических значений
data['total_area'].describe()
count 23158.000000 mean 57.703273 std 25.770748 min 12.000000 25% 40.000000 50% 51.400000 75% 68.100000 max 200.000000 Name: total_area, dtype: float64
#Проверяем наличие пустых значений.
data['rooms'].isna().sum()
0
#Удаляем выбросы
data = data[(data['rooms'] < 6)]
# Проверим выбросы
data.rooms.value_counts().to_frame()
| rooms | |
|---|---|
| 1 | 7984 |
| 2 | 7863 |
| 3 | 5698 |
| 4 | 1094 |
| 5 | 247 |
| 0 | 190 |
data['rooms'].describe()
count 23076.000000 mean 2.011397 std 0.954842 min 0.000000 25% 1.000000 50% 2.000000 75% 3.000000 max 5.000000 Name: rooms, dtype: float64
#Проверяем наличие пустых значений.
data['studio'].isna().sum()
0
# Используем метод describe() для получение статистических значений
data['studio'].describe()
count 23076 unique 2 top False freq 22930 Name: studio, dtype: object
# Проверяем уникальные значения
data['studio'].unique()
array([False, True])
# Проверим выбросы
data.studio.value_counts().to_frame()
| studio | |
|---|---|
| False | 22930 |
| True | 146 |
#Проверяем наличие пустых значений
data['open_plan'].isna().sum()
0
# Проверяем уникальные значения
data['open_plan'].unique()
array([False, True])
parks_nearest¶Пустых значений- 66%(крайне значительно), если решим изменить на '0, то повлияет на статистичекие показатели.\ Несколько вариантов:
если пустое значение в столбце parks_around3000 совпадает с 'locality_name' , заполняем медианным значением. Остальное заполняем 0.
Вывод: Не трогаем. Замена типа колонки на int64.
# Заполняем пустые значение медианным значением в разрезе места расположения. Медианну используем, тк есть экстремумы.
data['parks_nearest'] = data.groupby(['locality_name'])['parks_nearest'].apply(lambda x: x.fillna(x.median()))
#Заменим пропуски значением 0, используя метод fillna(), предположив, что парков в радиусе 3 км нет.
data['parks_nearest'] = data['parks_nearest'].fillna(0)
#Проверяем наличие пустых значений
data['parks_nearest'].isna().sum()
0
ponds_around3000¶Пустых значений- 23%(значительно), если решим изменить на '0, то повлияет на статистичекие показатели.\ Несколько вариантов:
если пустое значение в столбце ponds_around3000 совпадает с 'locality_name' , заполняем медианным значением,
Вывод: Выполняем уловие и остальное заполняем 0, тк скорее всего водоемов в радиусе 3 км нет.
# Заполняем пустые значение медианным значением в разрезе места расположения. Медианну используем, тк есть экстремумы.
data['ponds_around3000'] = data.groupby(['locality_name'])['ponds_around3000'].apply(lambda x: x.fillna(x.median()))
#Заменим пропуски значением 0, используя метод fillna()
data['ponds_around3000'] = data['ponds_around3000'].fillna(0)
#Проверяем наличие пустых значений
data['ponds_around3000'].isna().sum()
0
# Используем метод describe() для получение статистических значений
data['ponds_around3000'].describe()
count 23076.000000 mean 0.579867 std 0.873240 min 0.000000 25% 0.000000 50% 0.000000 75% 1.000000 max 3.000000 Name: ponds_around3000, dtype: float64
ponds_nearest¶Пустых значений- 62%(крайне значитально).
Несколько вариантов:
если пустое значение в столбце 'ponds_nearest' совпадает с 'locality_name', то заполняем медианным значением.
Вывод: После выполнения условия пустых значений осталось 21%. Пока оставим их.
# Заполняем пропуски в столбце 'ponds_nearest' медианными значениями по каждому типу 'locality_name'.
for i in data['locality_name'].unique():
data.loc[(data['locality_name'] == i) & (data['ponds_nearest'].isna()), 'ponds_nearest'] = \
data.loc[(data['locality_name'] == i), 'ponds_nearest'].median()
#Проверяем наличие пустых значений
data['ponds_nearest'].isna().sum()
5483
# Используем метод describe() для получение статистических значений
data['ponds_nearest'].describe()
count 17593.000000 mean 513.716279 std 200.083564 min 13.000000 25% 473.000000 50% 509.000000 75% 564.000000 max 1344.000000 Name: ponds_nearest, dtype: float64
# #Проверяем наличие пустых значений
data['last_price'].isna().sum()
0
# Меняем формат числа через pandas.options для читабельности
pd.options.display.float_format = '{: .2f}'.format
#Удаляем выбросы
data = data[(data['last_price'] > 500000) & (data['last_price'] < 30000000)]
# Используем метод describe() для получение статистических значений
data['last_price'].describe()
count 22896.00 mean 5585376.20 std 3728004.76 min 520000.00 25% 3400000.00 50% 4583000.00 75% 6500000.00 max 29999000.00 Name: last_price, dtype: float64
total_images¶Оставляем без изменений.
#Проверяем наличие пустых значений
data['total_images'].isna().sum()
0
# Используем метод describe() для получение статистических значений
data['total_images'].describe()
count 22896.00 mean 9.84 std 5.64 min 0.00 25% 6.00 50% 9.00 75% 14.00 max 50.00 Name: total_images, dtype: float64
#Проверяем наличие пустых значений по всем столбцам
data.isna().sum()
total_images 0 last_price 0 total_area 0 first_day_exposition 0 rooms 0 ceiling_height 2537 floors_total 0 living_area 1713 floor 0 is_apartment 0 studio 0 open_plan 0 kitchen_area 84 balcony 0 locality_name 0 airports_nearest 5459 cityCenters_nearest 0 parks_around3000 5439 parks_nearest 0 ponds_around3000 0 ponds_nearest 5466 days_exposition 0 total_area_split 1090 from_center 5440 dtype: int64
# check
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 22896 entries, 0 to 23322 Data columns (total 24 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 22896 non-null int64 1 last_price 22896 non-null float64 2 total_area 22896 non-null float64 3 first_day_exposition 22896 non-null datetime64[ns] 4 rooms 22896 non-null int64 5 ceiling_height 20359 non-null float64 6 floors_total 22896 non-null int64 7 living_area 21183 non-null float64 8 floor 22896 non-null int64 9 is_apartment 22896 non-null bool 10 studio 22896 non-null bool 11 open_plan 22896 non-null bool 12 kitchen_area 22812 non-null float64 13 balcony 22896 non-null int64 14 locality_name 22896 non-null object 15 airports_nearest 17437 non-null float64 16 cityCenters_nearest 22896 non-null float64 17 parks_around3000 17457 non-null float64 18 parks_nearest 22896 non-null float64 19 ponds_around3000 22896 non-null float64 20 ponds_nearest 17430 non-null float64 21 days_exposition 22896 non-null int64 22 total_area_split 21806 non-null object 23 from_center 17456 non-null object dtypes: bool(3), datetime64[ns](1), float64(11), int64(6), object(3) memory usage: 3.9+ MB
# check
# Показатели о кол-ве объявлений в датасете, минимальных и максимальных показателях
# в выбранных параметрах о продаже квартир
(
data[['rooms', 'total_area', 'ceiling_height', 'days_exposition', 'last_price', 'living_area', 'kitchen_area', 'floor',
'floors_total']]
.apply (['count', 'min', 'max'])
.style.format("{:,.2f}")
)
| rooms | total_area | ceiling_height | days_exposition | last_price | living_area | kitchen_area | floor | floors_total | |
|---|---|---|---|---|---|---|---|---|---|
| count | 22,896.00 | 22,896.00 | 20,359.00 | 22,896.00 | 22,896.00 | 21,183.00 | 22,812.00 | 22,896.00 | 22,896.00 |
| min | 0.00 | 12.00 | 2.40 | 0.00 | 520,000.00 | 2.00 | 0.00 | 1.00 | 1.00 |
| max | 5.00 | 200.00 | 5.80 | 1,580.00 | 29,999,000.00 | 164.70 | 54.20 | 33.00 | 36.00 |
# check
data.rooms.value_counts().to_frame()
| rooms | |
|---|---|
| 1 | 7970 |
| 2 | 7836 |
| 3 | 5618 |
| 4 | 1053 |
| 5 | 229 |
| 0 | 190 |
# check
# Показатели о кол-ве объявлений в датасете, минимальных и максимальных значениях
# в выбранных параметрах о продаже квартир
# сырые данные
(
data[['rooms', 'total_area', 'ceiling_height', 'days_exposition', 'last_price', 'living_area', 'kitchen_area',
'floor', 'floors_total']]
.apply (['count', 'min', 'max'])
.style.format("{:,.2f}")
)
| rooms | total_area | ceiling_height | days_exposition | last_price | living_area | kitchen_area | floor | floors_total | |
|---|---|---|---|---|---|---|---|---|---|
| count | 22,896.00 | 22,896.00 | 20,359.00 | 22,896.00 | 22,896.00 | 21,183.00 | 22,812.00 | 22,896.00 | 22,896.00 |
| min | 0.00 | 12.00 | 2.40 | 0.00 | 520,000.00 | 2.00 | 0.00 | 1.00 | 1.00 |
| max | 5.00 | 200.00 | 5.80 | 1,580.00 | 29,999,000.00 | 164.70 | 54.20 | 33.00 | 36.00 |
# check
data.rooms.value_counts().to_frame()
| rooms | |
|---|---|
| 1 | 7970 |
| 2 | 7836 |
| 3 | 5618 |
| 4 | 1053 |
| 5 | 229 |
| 0 | 190 |
# check
data.total_area.hist(bins = 150, figsize = (15,3));
# check
data.total_area.hist(bins = 150, figsize = (15,3), range = (180,500));
План работ:
'price_per_metr' цена одного квадратного метра;'first_day_exposition_of_week'день недели публикации объявления (0 — понедельник, 1 — вторник и так далее);'first_exposition_month' месяц публикации объявления;first_exposition_year год публикации объявления;type_of_floor тип этажа квартиры (значения — «первый», «последний», «другой»);distance_to_center расстояние до центра города в километрах (переведите из м в км и округлите до целых значений).# Рассчитаем цену м2 и добавим данные в новый столбец price_per_metr
data['price_per_metr'] = data['last_price']/data['total_area']
data.head(3)
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split | from_center | price_per_metr | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 20 | 13000000.00 | 108.00 | 2019-03-07 | 3 | 2.70 | 16 | 51.00 | 8 | False | ... | 18863.00 | 16028.00 | 1.00 | 482.00 | 2.00 | 755.00 | 0 | D | no_center_spb | 120370.37 |
| 1 | 7 | 3350000.00 | 40.40 | 2018-12-04 | 1 | 2.66 | 11 | 18.60 | 1 | False | ... | 12817.00 | 18603.00 | 0.00 | 0.00 | 0.00 | 584.50 | 81 | B | no_center_spb | 82920.79 |
| 2 | 10 | 5196000.00 | 56.00 | 2015-08-20 | 2 | 2.66 | 5 | 34.30 | 4 | False | ... | 21741.00 | 13933.00 | 1.00 | 90.00 | 2.00 | 574.00 | 558 | C | no_center_spb | 92785.71 |
3 rows × 25 columns
data['price_per_metr'].describe()
count 22896.00 mean 96262.55 std 35686.88 min 10185.19 25% 76239.15 50% 94545.45 75% 113149.85 max 848484.85 Name: price_per_metr, dtype: float64
#Удаляем выбросы
data = data[(data['price_per_metr'] < 320000)]
# Построим гистограмму
plt.figure(figsize=(15,5))
data.price_per_metr.hist(color='rebeccapurple', bins = 1000, alpha=0.6)
plt.xlabel('Средняя цена за м2')
plt.title('Распределение средней цены объектов за м2 в объявлениях')
plt.ylabel('Кол-во объявлений')
plt.show()
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['price_per_metr'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
0- понедельник 1- вторник 2- среда 3- четверг 4- пятница 5- суббота 6- воскресенье
# Добавим новый столбец first_day_exposition_of_week используя метод dayofweek
data['first_day_exposition_of_week'] = pd.to_datetime(data['first_day_exposition']).dt.dayofweek
data.head(3)
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split | from_center | price_per_metr | first_day_exposition_of_week | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 20 | 13000000.00 | 108.00 | 2019-03-07 | 3 | 2.70 | 16 | 51.00 | 8 | False | ... | 16028.00 | 1.00 | 482.00 | 2.00 | 755.00 | 0 | D | no_center_spb | 120370.37 | 3 |
| 1 | 7 | 3350000.00 | 40.40 | 2018-12-04 | 1 | 2.66 | 11 | 18.60 | 1 | False | ... | 18603.00 | 0.00 | 0.00 | 0.00 | 584.50 | 81 | B | no_center_spb | 82920.79 | 1 |
| 2 | 10 | 5196000.00 | 56.00 | 2015-08-20 | 2 | 2.66 | 5 | 34.30 | 4 | False | ... | 13933.00 | 1.00 | 90.00 | 2.00 | 574.00 | 558 | C | no_center_spb | 92785.71 | 3 |
3 rows × 26 columns
# Добавим новый столбец first_exposition_month используя метод month
data['first_exposition_month'] = pd.to_datetime(data['first_day_exposition']).dt.month
data.head(3)
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split | from_center | price_per_metr | first_day_exposition_of_week | first_exposition_month | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 20 | 13000000.00 | 108.00 | 2019-03-07 | 3 | 2.70 | 16 | 51.00 | 8 | False | ... | 1.00 | 482.00 | 2.00 | 755.00 | 0 | D | no_center_spb | 120370.37 | 3 | 3 |
| 1 | 7 | 3350000.00 | 40.40 | 2018-12-04 | 1 | 2.66 | 11 | 18.60 | 1 | False | ... | 0.00 | 0.00 | 0.00 | 584.50 | 81 | B | no_center_spb | 82920.79 | 1 | 12 |
| 2 | 10 | 5196000.00 | 56.00 | 2015-08-20 | 2 | 2.66 | 5 | 34.30 | 4 | False | ... | 1.00 | 90.00 | 2.00 | 574.00 | 558 | C | no_center_spb | 92785.71 | 3 | 8 |
3 rows × 27 columns
# Добавим новый столбец first_exposition_year используя метод year
data['first_exposition_year'] = pd.to_datetime(data['first_day_exposition']).dt.year
data.head(3)
| total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | total_area_split | from_center | price_per_metr | first_day_exposition_of_week | first_exposition_month | first_exposition_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 20 | 13000000.00 | 108.00 | 2019-03-07 | 3 | 2.70 | 16 | 51.00 | 8 | False | ... | 482.00 | 2.00 | 755.00 | 0 | D | no_center_spb | 120370.37 | 3 | 3 | 2019 |
| 1 | 7 | 3350000.00 | 40.40 | 2018-12-04 | 1 | 2.66 | 11 | 18.60 | 1 | False | ... | 0.00 | 0.00 | 584.50 | 81 | B | no_center_spb | 82920.79 | 1 | 12 | 2018 |
| 2 | 10 | 5196000.00 | 56.00 | 2015-08-20 | 2 | 2.66 | 5 | 34.30 | 4 | False | ... | 90.00 | 2.00 | 574.00 | 558 | C | no_center_spb | 92785.71 | 3 | 8 | 2015 |
3 rows × 28 columns
(значения- "первый", "последний", "другой")
# Добавим новый столбец number_floor и оборачиваем в функцию
def type_of_floor(row):
if row['floor'] == 1:
return 'первый'
elif row['floor'] == row['floors_total']:
return 'последний'
elif row['floor'] > 1 and row['floor'] < row['floors_total']:
return 'другой'
elif row['floor'] > 1 and math.isnan(row['floors_total']):
return 'другой'
data['type_of_floor'] = data.apply(type_of_floor, axis=1)
data[['floor','floors_total', 'type_of_floor']].sample(3)
| floor | floors_total | type_of_floor | |
|---|---|---|---|
| 9021 | 4 | 15 | другой |
| 17089 | 7 | 15 | другой |
| 1136 | 1 | 5 | первый |
(переводим из м в км и округляем до целых значений)
# Добавим новый столбец distance_to_center используя метод round() для округления
###Сначала убери пустые значения в cityCenters_nearest
data['distance_to_center'] = np.round(data['cityCenters_nearest']/1000).astype('int')
data[['cityCenters_nearest','distance_to_center',]].head(3)
| cityCenters_nearest | distance_to_center | |
|---|---|---|
| 0 | 16028.00 | 16 |
| 1 | 18603.00 | 19 |
| 2 | 13933.00 | 14 |
План работ:
2.3.1. Изучу, построю гистограммы и опишу все мои наблюдения для каждого из этих параметров, а именно:
2.3.2. Проанализирую столбец days_exposition(срок продажи):
2.3.3. Определю факторы влияния на общую(полную) стоимость объекта через корреляцию следующих параметров:
2.3.4. Проанализирую столбец locality_name:
Использую следующие методы:
# Оборачиваем график в функцию для дальнейшего использования
def building_hist(data, x_name,y_name, title):
plt.figure(figsize=(10,5))
data.hist(color='rebeccapurple', bins = 100, alpha=0.6 )
plt.xlabel(x_name)
plt.ylabel(y_name)
plt.title(title)
plt.ticklabel_format(style='plain')
plt.show()
'total_area'-общая площадь
# Построим гистограмму
building_hist(data.total_area,'Общая площадь(м2)', 'Кол-во объявлений','Распределение общей площади в объявлениях')
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['total_area'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на общую площадь квартир от 50 до 100 м2.
'living_area'-жилая площадь
# Построим гистограмму
building_hist(data.living_area,'Жилая площадь(м2)', 'Кол-во объявлений','Распределение жилой площади в объявлениях')
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['living_area'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на жилую площадь квартир от 30 до 50 м2.
'kitchen_area'-площадь кухни
# Построим гистограмму
building_hist(data.kitchen_area,'Площадь кухни(м2)', 'Кол-во объявлений','Распределение площадей кухнь в объявлениях')
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['kitchen_area'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на площадь кухонь от 7 до 12 м2. Есть незначительное количество объявлений, где площадь кухни от 30 до 55 м2.
'last_price'- цена объекта
# Построим гистограмму
plt.figure(figsize=(15,5))
data.last_price.hist(color='rebeccapurple', bins = 1000, alpha=0.5)
plt.xlabel('Цена объекта(0.5=5млн руб)')
plt.title('Распределение цен объектов в объявлениях')
plt.ylabel('Кол-во объявлений')
plt.show()
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['last_price'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на диапозон цен объекта в районе 5 млн рублей. Большое количество выбросов до 500К и более 250 млн р.
'rooms'- количество комнат
# Построим гистограмму
building_hist(data.rooms,'Количество комнат', 'Кол-во объявлений', 'Распределение по количеству комнат в объявлениях')
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['rooms'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на количество комнат до 2х комнат.
'ceiling_height'-высота потолков
# Построим гистограмму
building_hist(data.ceiling_height,'Высота потолков','Кол-во объявлений','Распределение по высоте потолков в объявлениях')
Наблюдение: На графике видно, что большая часть объявлений приходится на высоту потолков в районе 2,7 метров.
'floor'- этаж квартиры
# Построим гистограмму
building_hist(data.floor,'Этаж квартиры', 'Кол-во объявлений', 'Распределение по этажам в объявлениях')
Наблюдение: На графике видно, что большая часть объявлений приходится на квартиры, расположенные на нижних этажаж( от 1 по 3).
type_of_floor -тип этажа квартиры («первый», «последний», «другой»)
# Построим гистограмму
plt.figure(figsize=(10,3))
data.type_of_floor.hist(color='rebeccapurple', bins = 5, alpha=0.6)
plt.xlabel('Тип этажа квартиры')
plt.ylabel('Кол-во объявлений')
plt.title('Распределение по типам этажей в объявлениях')
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на квартиры, расположенные между первым и последним этажами.
'floors_total'- общее количество этажей в доме
# Построим гистограмму
building_hist(data.floors_total,'Общее количество этажей в доме', 'Кол-во объявлений', 'Распределение по общему количеству этажей в доме в объявлениях')
Наблюдение: На графике видно, что большая часть объявлений приходится на 'этажность дома (5, 9).
'cityCenters_nearest' - расстояние до центра города в метрах
# Построим гистограмму
building_hist(data.cityCenters_nearest,'Расстояние до центра города в метрах', 'Кол-во объявлений', 'Распределение по расстоянию до центра города в метрах')
Наблюдение: На графике видно, что большая часть объявлений приходится на квартиры, расположенные в 10-20 км от центра города. Меньше 0- заглушки
'airports_nearest'- расстояние до ближайшего аэропорта
# Построим гистограмму
building_hist(data.airports_nearest,'Расстояние до ближайшего аэропорта','Кол-во объявлений', 'Распределение по расстоянию до ближайшего аэропорта')
Наблюдение: На графике видно, что большая часть объявлений приходится на квартиры, расположенные в районе 20 км от аэропорта.
parks_nearest- расстояние до ближайшего парка
# Построим гистограмму
plt.figure(figsize=(15,5))
data.last_price.hist(color='rebeccapurple', bins = 500, alpha=0.5)
plt.xlabel('Расстояние до ближайшего парка')
plt.ylabel('Кол-во объявлений')
plt.title('Распределение по расстоянию до ближайшего парка')
plt.show()
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['parks_nearest'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на квартиры, расположенные в районе до 500 м от парка.
'first_day_exposition_of_week', 'first_exposition_month'день и месяц публикации объявления
# Построим гистограмму
building_hist(data.first_day_exposition_of_week,'День недели', 'Кол-во объявлений','Распределение по дням недели в объявлениях')
Наблюдение: На графике видно, что большая часть объявлений приходится на будни. Незначительные пики во вторник и четверг.
# Построим гистограмму
building_hist(data.first_exposition_month,'Месяц', 'Кол-во объявлений', 'Распределение по месяцам в объявлениях')
Наблюдение: На графике видно, что большая часть объявлений приходится на пиковые месяца- январь и ноябрь.
# Рассчитаю медианную величину
data['days_exposition'].median()
73.0
# Рассчитаю среднюю величину
data['days_exposition'].mean()
154.9141208863249
# Построим гистограмму
plt.figure(figsize=(15,5))
data.days_exposition.hist(color='rebeccapurple', bins = 500, alpha=0.6)
plt.xlabel('Длительность размещения объяления')
plt.ylabel('Кол-во объявлений')
plt.title('Распределение объявлений по длительности размещения')
# Построим диаграмму размаха("ящик с усами")
fig = plt.figure(figsize=(15, 5))
ax = plt.subplot(2, 1,2)
ax.boxplot(data['days_exposition'], False, sym='rs', vert=False, whis=0.5, positions=[0], widths=[0.3])
plt.tight_layout()
plt.show()
Наблюдение: На графике видно, что большая часть объявлений приходится на длительность размещения от 55 до 60 дней. Основные выбросы приходяться на длительность от 170 дней. Данные смещены в левую сторону, что говорит о быстрых продажах.
Разделю срок продажи на "быстрые" и "долгие"
# быстрой продажей будем считать что меньше 25% и долгой, то что больше 75%
data['days_exposition'].describe()
count 22881.00 mean 154.91 std 210.78 min 0.00 25% 23.00 50% 73.00 75% 196.00 max 1580.00 Name: days_exposition, dtype: float64
# Добавим новый столбец days_exposition_duration и оборачиваем в функцию
def days_exposition_duration(row):
if row['days_exposition'] < 22:
return 'быстрые'
elif row['days_exposition'] > 201:
return 'долгие'
return 'стандартные'
data['days_exposition_duration'] = data.apply(days_exposition_duration, axis=1)
data[['days_exposition','days_exposition_duration']].sample(3)
| days_exposition | days_exposition_duration | |
|---|---|---|
| 6251 | 177 | стандартные |
| 10996 | 209 | долгие |
| 11318 | 562 | долгие |
# Изменение средней скорости продаж по годам
data.groupby('first_exposition_year')['days_exposition'].mean()
first_exposition_year 2014 780.32 2015 610.28 2016 314.38 2017 153.09 2018 83.64 2019 11.68 Name: days_exposition, dtype: float64
Вывод: Согласно таблице выше, видно, что с 2014 по 2018 год (2019 исключим из-за неполныл данных) скорость продаж увеличилась в 9 раз, что говорит о высоком спросе на рынке недвижимости.
# Построим корреляционную матрицу
data_corr = data[['last_price', 'total_area', 'living_area', 'kitchen_area', 'rooms', 'airports_nearest', \
'balcony', 'ceiling_height', 'cityCenters_nearest', 'floor', 'floors_total', 'is_apartment', \
'locality_name', 'parks_around3000', 'parks_nearest', 'ponds_around3000',\
'ponds_nearest', 'total_images', 'price_per_metr','distance_to_center']].corr()
data_corr.style.background_gradient(cmap='BuPu').set_precision(2)
| last_price | total_area | living_area | kitchen_area | rooms | airports_nearest | balcony | ceiling_height | cityCenters_nearest | floor | floors_total | is_apartment | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | total_images | price_per_metr | distance_to_center | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| last_price | 1.00 | 0.78 | 0.67 | 0.39 | 0.48 | -0.03 | 0.04 | 0.47 | 0.02 | 0.09 | 0.10 | 0.01 | 0.17 | 0.31 | 0.30 | -0.07 | 0.17 | 0.68 | 0.02 |
| total_area | 0.78 | 1.00 | 0.92 | 0.40 | 0.79 | -0.02 | 0.04 | 0.40 | -0.04 | -0.02 | -0.04 | 0.01 | 0.13 | 0.12 | 0.18 | -0.05 | 0.12 | 0.15 | -0.05 |
| living_area | 0.67 | 0.92 | 1.00 | 0.32 | 0.87 | -0.05 | 0.01 | 0.36 | -0.05 | -0.11 | -0.16 | 0.01 | 0.14 | 0.09 | 0.15 | -0.04 | 0.11 | 0.04 | -0.05 |
| kitchen_area | 0.39 | 0.40 | 0.32 | 1.00 | 0.18 | 0.02 | -0.02 | 0.23 | -0.06 | 0.10 | 0.14 | 0.02 | 0.04 | 0.05 | 0.09 | -0.03 | 0.01 | 0.18 | -0.06 |
| rooms | 0.48 | 0.79 | 0.87 | 0.18 | 1.00 | -0.05 | -0.00 | 0.20 | -0.03 | -0.16 | -0.22 | -0.01 | 0.10 | 0.05 | 0.09 | -0.02 | 0.10 | -0.08 | -0.03 |
| airports_nearest | -0.03 | -0.02 | -0.05 | 0.02 | -0.05 | 1.00 | 0.05 | -0.11 | 0.27 | 0.07 | 0.11 | 0.02 | 0.01 | 0.03 | -0.05 | 0.01 | -0.00 | -0.04 | 0.27 |
| balcony | 0.04 | 0.04 | 0.01 | -0.02 | -0.00 | 0.05 | 1.00 | -0.08 | 0.03 | 0.17 | 0.17 | 0.03 | -0.08 | -0.00 | -0.04 | 0.01 | 0.05 | 0.04 | 0.03 |
| ceiling_height | 0.47 | 0.40 | 0.36 | 0.23 | 0.20 | -0.11 | -0.08 | 1.00 | -0.22 | -0.07 | -0.11 | 0.06 | 0.23 | 0.09 | 0.25 | -0.07 | 0.02 | 0.34 | -0.22 |
| cityCenters_nearest | 0.02 | -0.04 | -0.05 | -0.06 | -0.03 | 0.27 | 0.03 | -0.22 | 1.00 | 0.06 | 0.09 | 0.02 | -0.14 | 0.36 | 0.20 | -0.02 | 0.01 | 0.14 | 1.00 |
| floor | 0.09 | -0.02 | -0.11 | 0.10 | -0.16 | 0.07 | 0.17 | -0.07 | 0.06 | 1.00 | 0.68 | -0.01 | -0.16 | 0.10 | -0.03 | 0.03 | 0.03 | 0.21 | 0.06 |
| floors_total | 0.10 | -0.04 | -0.16 | 0.14 | -0.22 | 0.11 | 0.17 | -0.11 | 0.09 | 0.68 | 1.00 | -0.02 | -0.25 | 0.14 | -0.04 | 0.05 | 0.01 | 0.27 | 0.09 |
| is_apartment | 0.01 | 0.01 | 0.01 | 0.02 | -0.01 | 0.02 | 0.03 | 0.06 | 0.02 | -0.01 | -0.02 | 1.00 | 0.00 | -0.00 | 0.01 | 0.01 | 0.02 | 0.02 | 0.02 |
| parks_around3000 | 0.17 | 0.13 | 0.14 | 0.04 | 0.10 | 0.01 | -0.08 | 0.23 | -0.14 | -0.16 | -0.25 | 0.00 | 1.00 | -0.03 | 0.29 | -0.12 | 0.01 | 0.14 | -0.14 |
| parks_nearest | 0.31 | 0.12 | 0.09 | 0.05 | 0.05 | 0.03 | -0.00 | 0.09 | 0.36 | 0.10 | 0.14 | -0.00 | -0.03 | 1.00 | 0.31 | -0.01 | 0.04 | 0.44 | 0.36 |
| ponds_around3000 | 0.30 | 0.18 | 0.15 | 0.09 | 0.09 | -0.05 | -0.04 | 0.25 | 0.20 | -0.03 | -0.04 | 0.01 | 0.29 | 0.31 | 1.00 | -0.15 | 0.00 | 0.31 | 0.20 |
| ponds_nearest | -0.07 | -0.05 | -0.04 | -0.03 | -0.02 | 0.01 | 0.01 | -0.07 | -0.02 | 0.03 | 0.05 | 0.01 | -0.12 | -0.01 | -0.15 | 1.00 | -0.00 | -0.05 | -0.02 |
| total_images | 0.17 | 0.12 | 0.11 | 0.01 | 0.10 | -0.00 | 0.05 | 0.02 | 0.01 | 0.03 | 0.01 | 0.02 | 0.01 | 0.04 | 0.00 | -0.00 | 1.00 | 0.14 | 0.01 |
| price_per_metr | 0.68 | 0.15 | 0.04 | 0.18 | -0.08 | -0.04 | 0.04 | 0.34 | 0.14 | 0.21 | 0.27 | 0.02 | 0.14 | 0.44 | 0.31 | -0.05 | 0.14 | 1.00 | 0.14 |
| distance_to_center | 0.02 | -0.05 | -0.05 | -0.06 | -0.03 | 0.27 | 0.03 | -0.22 | 1.00 | 0.06 | 0.09 | 0.02 | -0.14 | 0.36 | 0.20 | -0.02 | 0.01 | 0.14 | 1.00 |
fig = px.imshow(data_corr)
fig.show()
Изучим влияние средней стомости за м2 на стоимость квартиры
aver_price_year = data.groupby('first_exposition_year')['price_per_metr'].mean()
aver_price_year.plot(x = 'first_exposition_year', y = 'price_per_metr', kind = 'line', color='rebeccapurple',\
alpha=0.6, figsize=(10,5), xlabel='Год размещения объявления', ylabel='Стоимость м2')
plt.title('Влияние средней стомости за м2 на стоимость квартиры по годам')
plt.show()
Наблюдение: На графике четко прослеживается падение средней стоимости за м2 с 2014 по 2015 года, стагнация с 2016 по 2017 года и резкий подъем с 2019 года.
Изучим влияние месяца размещения на стоимость квартиры
aver_price_month = data.groupby('first_exposition_month')['price_per_metr'].mean()
aver_price_month.plot(x = 'first_exposition_month', y = 'price_per_metr', kind = 'line', color='rebeccapurple',\
alpha=0.6, figsize=(10,5), xlabel='Месяц размещения объявления', ylabel='Стоимость за м2')
plt.title('Влияние средней стомости за м2 на стоимость квартиры по месяцам')
plt.show()
Наблюдение: На графике видно, что основной пик роста средней стоимости за м2 приходит на апрель.
# check
data.pivot_table(index='last_price', values=['total_area', 'living_area', 'distance_to_center',\
'rooms','kitchen_area']).reset_index()
| last_price | distance_to_center | kitchen_area | living_area | rooms | total_area | |
|---|---|---|---|---|---|---|
| 0 | 520000.00 | -1.00 | 5.50 | 18.00 | 1.00 | 30.00 |
| 1 | 530000.00 | -1.00 | 5.80 | 16.00 | 1.00 | 29.60 |
| 2 | 550000.00 | -1.00 | 10.45 | 23.75 | 1.50 | 37.97 |
| 3 | 560000.00 | -1.00 | 5.90 | 21.40 | 1.33 | 37.50 |
| 4 | 565000.00 | -1.00 | 9.00 | 25.00 | 2.00 | 45.00 |
| ... | ... | ... | ... | ... | ... | ... |
| 2628 | 29500000.00 | 16.00 | 19.75 | 83.17 | 3.50 | 149.50 |
| 2629 | 29800000.00 | 5.00 | 16.00 | 55.90 | 3.00 | 97.00 |
| 2630 | 29900000.00 | 4.00 | 11.00 | 87.80 | 3.00 | 131.90 |
| 2631 | 29990000.00 | 9.00 | 25.50 | 91.70 | 3.00 | 186.00 |
| 2632 | 29999000.00 | 6.67 | 14.27 | 88.37 | 3.67 | 137.13 |
2633 rows × 6 columns
Изучим влияние общей/жилой площади/площади кухни/количества комнат и расстояния от центра на стоимость квартиры\ Напримере рассмотрим трехкомнатные квартиры и общее количество объектов
# цена-общая площадь
data[data['rooms'] == 3].query('total_area < 201 and last_price < 25_000_000').plot(kind='scatter',
y='last_price' , x='total_area', alpha=0.5, subplots=True, figsize=(15,8), c = 'b', s = 4)
plt.title('Диаграмма рассеяния — Общая площадь — цена трешки')
# цена-жилая площадь
data[data['rooms'] == 3].query('total_area < 201 and last_price < 25_000_000').plot(kind='scatter',
y='last_price' , x='living_area', alpha=0.5, figsize=(15,8), c = 'r', s = 4)
plt.title('Диаграмма рассеяния — Жилая площадь — цена трешки');
# цена-площадь кухни
data[data['rooms'] == 3].query('total_area < 201 and last_price < 25_000_000').plot(kind='scatter',
y='last_price' , x='kitchen_area', alpha=0.5, figsize=(15,8), c = 'g', s = 4)
plt.title('Диаграмма рассеяния — Площадь кухни — цена трешки');
# цена-этаж
data[data['rooms'] == 3].query('total_area < 201 and last_price < 25_000_000').plot(kind='scatter',
y='last_price' , x='floor', alpha=0.5, figsize=(15,8), c = 'c', s = 4)
plt.title('Диаграмма рассеяния — Этаж — цена трешки');
Наблюдение: Проанализировав трехкомнахные квартиры, видна четкая зависимости цены от общей и жилой площади, а также площади кухни.
# Кроме этого, мы можем посмотреть зависимости цены и общей площади по всем объявлениям
data.plot(kind='scatter', x='last_price', y='total_area', figsize=(10,5), color='rebeccapurple', alpha= 0.1)
data[['last_price','total_area']].corr()
| last_price | total_area | |
|---|---|---|
| last_price | 1.00 | 0.78 |
| total_area | 0.78 | 1.00 |
Наблюдение: На графике видна высокая корреляция (0.78) между стоимостью квартиры и общей площадью, \ чем больше площадь, тем выше цена. Кроме этого видны объекты с завышенами ценами.
# мы можем посмотреть зависимости цены и жилой площади по всем объявлениям
data.plot(kind='scatter', x='last_price', y='living_area', figsize=(10,5), color='rebeccapurple', alpha= 0.1)
data[['last_price','living_area']].corr()
| last_price | living_area | |
|---|---|---|
| last_price | 1.00 | 0.67 |
| living_area | 0.67 | 1.00 |
Наблюдение: На графике видна заметная корреляция (0.67) между стоимостью квартиры и жилой площадью.
# Кроме этого,мы можем посмотреть зависимости цены и площади кухни по всем объявлениям
data.plot(kind='scatter', x='last_price', y='kitchen_area', figsize=(10,5), color='rebeccapurple', alpha= 0.1)
data[['last_price','kitchen_area']].corr()
| last_price | kitchen_area | |
|---|---|---|
| last_price | 1.00 | 0.39 |
| kitchen_area | 0.39 | 1.00 |
Наблюдение: На графике видна заметная корреляция (0.59) между стоимостью квартиры и площадью кухнию.
# Мы можем посмотреть зависимости цены и количества комнат по всем объявлениям
data.plot(kind='scatter', x='last_price', y='rooms', figsize=(10,5), color='rebeccapurple', alpha= 0.1)
data[['last_price','rooms']].corr()
| last_price | rooms | |
|---|---|---|
| last_price | 1.00 | 0.48 |
| rooms | 0.48 | 1.00 |
Наблюдение: На графике видна умеренная корреляция (0.48) между стоимостью квартиры и количеством комнат.
# Мы можем посмотреть зависимости цены и типа этажа по всем объявлениям
data.plot(kind='scatter', x='last_price', y='type_of_floor', figsize=(10,5), color='rebeccapurple', alpha= 0.1)
<AxesSubplot:xlabel='last_price', ylabel='type_of_floor'>
Наблюдение: На графике видно, что квартиры на первом этаже значительно дешевле, чем квартиры расположенные между первым и последним этажами.
# Мы можем посмотреть зависимость цены и удаленности от центра по всем объявлениям
data.plot(kind='scatter', x='last_price', y='cityCenters_nearest', figsize=(10,5),\
color='rebeccapurple', alpha= 0.1)
<AxesSubplot:xlabel='last_price', ylabel='cityCenters_nearest'>
Наблюдение: На графике видно, чем ближе к центру, тем дороже.
cities_top10 = data.pivot_table(index='locality_name', values= 'price_per_metr', aggfunc=['mean', 'count'])
cities_top10.columns = ['price_per_metr_mean', 'total_flats']
price_for_cities_top10 = cities_top10.sort_values(by= 'total_flats', ascending = False).head(10)
price_for_cities_top10.sort_values(by= 'price_per_metr_mean', ascending = False)
| price_per_metr_mean | total_flats | |
|---|---|---|
| locality_name | ||
| Санкт-Петербург | 110212.96 | 15047 |
| Пушкин | 102828.42 | 359 |
| деревня Кудрово | 92473.55 | 299 |
| поселок Парголово | 90332.26 | 326 |
| поселок Мурино | 85588.35 | 549 |
| поселок Шушары | 78551.34 | 439 |
| Колпино | 75333.30 | 337 |
| Гатчина | 68757.68 | 306 |
| Всеволожск | 67321.03 | 395 |
| Выборг | 58238.14 | 235 |
price_for_cities_low10 = cities_top10.sort_values(by= 'total_flats', ascending = True).head(10)
price_for_cities_low10.sort_values(by= 'price_per_metr_mean', ascending = True)
| price_per_metr_mean | total_flats | |
|---|---|---|
| locality_name | ||
| деревня Пельгора | 18269.23 | 1 |
| деревня Мануйлово | 24476.99 | 1 |
| поселок Коммунары | 25139.66 | 1 |
| поселок Каложицы | 26715.69 | 1 |
| деревня Меньково | 28828.83 | 1 |
| поселок Кирпичное | 33105.02 | 1 |
| село Шум | 33898.31 | 1 |
| деревня Мины | 35714.29 | 1 |
| деревня Нижние Осельки | 39864.86 | 1 |
| деревня Новолисино | 46848.38 | 1 |
fig, ax = plt.subplots(figsize=(15, 5))
# 10 населённых пунктов с наибольшим числом объявлений
data_top_places = data[data.locality_name.isin(data.locality_name.value_counts().index[:10])]
# Пишем функцию
for locality in data_top_places.locality_name.unique():
sns.kdeplot(data_top_places[data_top_places.locality_name == locality].price_per_metr, label = locality)
plt.grid(True) # сетка
plt.legend(loc = 'upper left', bbox_to_anchor = (1,1)) # положение легенды
plt.title('Плотность распределения\цены за квадратный метр по населенным пунктам', loc = 'left') # название графика
plt.xlabel('Цена за квадратный метр') # подпись оси x
plt.xlim((0,250000)) # ограничение значений оси X
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: '{}'.format(int(x/1000)) + 'K')) # форматирование подписей на оси X
plt.annotate('Хвосты', size = 15, xy = (200000, 0.000003), xytext = (220000, 0.0000225),
arrowprops = dict(facecolor = 'gray', shrink = 0.1, width = 2)) # аннотация графика с заданной позицией
plt.show()
Наблюдение: Выбрав топ 10 населенных пунктов со средней стоимостью за м2, мы видим, что цена находится в диапазоне 70К-120К в зависимости от места. Максимум в пунктах- Санкт-Петербург, Пушкин, Сестрорецк. Также есть выбросы от 200К.
Определю среднюю цену каждого километра в Санкт-Петербурге и опишу, как стоимость объектов зависит от расстояния до центра города.
# Сортируем по городу, расстоянию и цене
data_spb_mean = data.query('locality_name=="Санкт-Петербург"')\
.groupby('distance_to_center')['last_price'].mean().plot(grid=True, figsize=(12, 5), color='rebeccapurple')
plt.title('Средняя цена км в Санкт-Петербурге') # название графика
plt.xlabel('Расстояние от центра (км)') # подпись оси x
plt.ylabel('Средняя цена за км')
plt.show()
Наблюдение: На графике четко видна корреляция между стоимостью квартиры и расстоянием до центра. Чем ближе к центру, тем дороже.
Изучив данные сервиса Яндекс.Недвижимость — архив объявлений о продаже квартир в Санкт-Петербурге и соседних населённых пунктов за несколько лет было выявлено следующее:
Основные аномалии были определены по причине некорректно введенных данных самими пользователями, а именно:
Если в данных наблюдались пропуски, они заменялись на среднию или медианную величину в разрезе каждого типа данных. Если данные не были найдены через уникальные значения, они обнулялись или удалялись, в зависимости от влияния на статистику. Часть данных, в основном, которая была получена картографическим путем не трогалась, если нельзя было их заменить через функции в разрезе названия населенного пункта.
Высокую корреляционную зависимость можно увидеть между last_price- rooms, rooms-total_area, total_area-living_area, kitchen_area-total_area и last_price-distance_to_center
Выбрав топ 10 населенных пунктов со средней стоимостью за м2, мы видим, что цена находится в диапазоне 70К-120К в зависимости от места. Максимум в пунктах- Санкт-Петербург, Пушкин, Сестрорецк.
Кроме этого, проанализировав динамику средней цены за м2 по годам, четко прослеживается падение средней стоимости за м2 с 2014 по 2015 года, стагнация с 2016 по 2017 года и резкий подъем с 2019 года.
</b>